package org.xteam.sled.semantic;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.xteam.parser.runtime.AstList;
import org.xteam.parser.runtime.Span;
import org.xteam.sled.ast.Expr;
import org.xteam.sled.ast.IdentExpr;
import org.xteam.sled.model.LabelledPattern;
import org.xteam.sled.model.LatentPattern;
import org.xteam.sled.semantic.exp.Exp;
import org.xteam.sled.semantic.exp.ExpBindingInstance;
import org.xteam.sled.semantic.exp.ExpConst;
import org.xteam.sled.semantic.exp.ExpVar;
import org.xteam.sled.semantic.exp.VariableSubstitute;


public class InstructionConstructor {

	private String opcode;
	private List<Operand> operands;
	private ConstructorType constype;
	private List<LatentPattern> branches;
	private List<OperandSyntax> syntax;

	public InstructionConstructor(String opcode, List<Operand> operands,
			ConstructorType constype, List<LatentPattern> branches, List<OperandSyntax> syntax) {
		this.opcode = opcode;
		this.operands = operands;
		this.constype = constype;
		this.branches = branches;
		this.syntax = syntax;
		constype.addMember(this);
	}

	public String name() {
		return opcode;
	}
	
	public List<Operand> getOperands() {
		return operands;
	}
	
	public Operand getOperand(String name) {
		for (Operand op : operands) {
			if (op.name().equals(name)) {
				return op;
			}
		}
		throw new RuntimeException("Constructor " + opcode + " has no input named " + name);
	}
	
	public List<LatentPattern> getBranches() {
		return branches;
	}
	
	public List<OperandSyntax> getOperandSyntax() {
		return syntax;
	}

	public LabelledPattern apply(AstList<Expr> arguments, Span span, IEvaluationContext context) {
		if (arguments.size() != operands.size()) {
			context.reportError(span, name() + " expects " + operands.size()
					+ " arguments, but you gave " + arguments.size());
			return null;
		}
		Map<String, Exp> argTable = new HashMap<String, Exp>();
		boolean isEncoding = false; // XXX
		for (int i = 0; i < arguments.size(); ++i) {
			Operand op = operands.get(i);
			Expr e = arguments.get(i);
			if (op.getType() instanceof InstanceOperand) {
				ConstructorType constype = ((InstanceOperand) op.getType()).getConstructorType();
				if (e instanceof IdentExpr) {
					String n = ((IdentExpr)e).getName();
					if (isEncoding) {
						IDenotable d = context.lookup(n, e.span());
						/*if (d instanceof Operand) {
							Operand opp = (Operand) d;
							// opp.getType()
							//of D.OPERAND (_, C.INSTANCE constypep, _)
							if (constypep == constype)
								argTable.put(op.name(), new ExpVar(n));
							else {
								throw new RuntimeException("expected operand or " +  nounOfType("constructor application", constype)
									+ " but got " + nounOfType("something", constypep) + "instead");
							}
						}*/
						throw new RuntimeException("expected " + nounOfType ("operand or constructor application", constype));
					} else {
						argTable.put(op.name(), new ExpBindingInstance (n, false, constype));
					}
				} else {
					throw new RuntimeException("not completed");
				}
				/*(case e
				         of A.APP ((opcode, rgnp), args) =>
				              let fun fromConslist [cons] = 
				                        let val {ty, ...} = C.info cons
				                            fun fromInstance i = newTable(instanceExp i)
				                            val constypep = C.typeid ty
				                        in  if constypep = constype then
				                              emap fromInstance (constructorInstance lookup (rgnp, cons,args))
				                            else
				                              throw new Error("expected operand or " + nounOfType("constructor application", constype) +
							                    " but got " + nounOfType("something", constypep) + "instead");
				                        end
				                    | fromConslist l = Error.errorl (
				                          ["opcode stands for ", Int.toString (length l), " constructors"]
				                                                     @ consNames 3 ": " l)
				                                                     
				                  and consNames _ pfx [] = []
				                    | consNames 0 pfx _ = [pfx, "..."]
				                    | consNames k pfx (h::t) =
				                        pfx :: #name (C.info h) :: consNames (k-1) ", " t
				              in  strip (emap fromConslist (opcode2cons_s (opcode, rgnp)))
				              end
				          | A.IDENT n =>
				              (case elaborationContext
				                 of ENCODING =>
				                      (case lookup n
				                         of D.OPERAND (_, C.INSTANCE constype', _) =>
				                              if constype' = constype then
				                                OK (newTable (Exp.var n))
				                              else
				                                Error.errorl ["expected operand or ", nounOfType "constructor application" constype,
								              " but got ", nounOfType "something" constype', "instead"]
				                          | _ => Error.errorl ["expected ", nounOfType "operand or constructor application" constype])
				                  | MATCHING => OK (newTable (Exp.bindingInstance (n,false,constype))))
				          | A.NARY (A.WILDCARD, []) => 
				              (case elaborationContext
				                 of ENCODING => error "cannot use wildcard _ in encoding"
				                  | MATCHING => 
				                       OK (newTable (Exp.bindingInstance (Wildcard.new(), true, constype))))
				          | A.NARY _ => error "expected constructor; found integer expression"
				          | A.INT _ => error "expected constructor; found integer literal"
				          | A.LITERAL_exp _ => error "expected constructor; found string literal"
				          | A.MARK_EXP (rgn, exp) => catch rgn (addArg lookup)
				                                     (((name, ty, prop), exp), OK (sourcemap, rgn, table)))
				                                     */
			} else /* if (op.geType() instanceof IntegerOperand) */{
				Field field = op.getField();
				if (field != null) {
					if (e instanceof IdentExpr) {
						String s = ((IdentExpr)e).getName();
						Integer value = context.getNamesOf(field).get(s);
						if (value != null) {
							argTable.put(op.name(), new ExpConst(value));
						} else {
							context.reportError(e.span(), s + " is not a value of field " + field.name());
						}
					} else {
						Exp n = new IntegerSemantic(new FieldLookupEvaluationContext(context, field)).analyse(e);
						argTable.put(op.name(), n.insistSingle(context, e.span()));
					}
				} else {
					Exp n = new IntegerSemantic(context).analyse(e);
					argTable.put(op.name(), n.insistSingle(context, e.span()));
				}
				
			}
		}
		VariableSubstitute subs = new VariableSubstitute(argTable);
		LabelledPattern res = null;
		for (LatentPattern pat : branches) {
			LabelledPattern n = pat.injectLatent().subst(subs);
			if (res == null)
				res = n;
			else
				res = res.or(n);
		}
		//res = res.subst(new ExpSimplifier());
		return new LabelledPattern(res).nameUnnamed(name());
	}
	
	private String nounOfType(String str, ConstructorType constype) {
		if (constype.name() != null)
			return str + " of type " + constype.name();
		return "untyped " + str;
	}

	private static class FieldLookupEvaluationContext extends ForwardingEvaluationContext {

		private Field field;

		public FieldLookupEvaluationContext(IEvaluationContext context, Field field) {
			super(context);
			this.field = field;
		}
		
		@Override
		public IDenotable lookup(String name, Span span) {
			Integer value = context.getNamesOf(field).get(name);
			if (value != null) {
				if (isInteger(name, span))
					context.reportError(span, "value " + name + " = " + value + " of field "
		                  + field.name() + " hides integer " + name + " in outer scope (source coordinates may be off)");
				return new Temporary(new ExpConst(value), null);
			}
			return context.lookup(name, span);
		}
		
		private boolean isInteger(String name, Span span) {
			return context.lookup(name, span).projectInt(context, span) != null;
		}
	}
	
	public String toString() {
		return name();
	}

	

}
